package net.flowas.genericcrud.service.impl;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.Map;
import java.util.function.Function;
import javax.imageio.ImageIO;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import net.flowas.genericcrud.model.FileInfo;


/**
 * 生成压缩图.
 *
 */
public class ImageScale implements Function<FileInfo, Map> {
  private static final Logger LOG = LoggerFactory.getLogger(ImageScale.class);
  private static final double SUPPORT = 3.0;
  private double[] contrib;
  private double[] normContrib;
  private double[] tmpContrib;
  private int dots;
  private int halfDots;
  //可以配置的变量
  private String fileStoragePath;
  private int thumbnailWidth;
  private int thumbnailHeight;

  @Override
  public Map apply(FileInfo fileInfo) {
    File sourceFile = Paths.get(fileStoragePath, fileInfo.getFilePath()).toFile();
    String fileName = sourceFile.getName();
    int index = fileName.lastIndexOf('.');
    String ext = fileName.substring(index);
    fileName = fileName.substring(0, fileName.lastIndexOf('.'));
    String workDir = sourceFile.getParent() + File.separator + fileName;
    File dir = new File(workDir);
    if (!dir.exists()) {
      dir.mkdir();
    }
    String target = workDir + File.separator + "thambnail" + ext;
    this.scale(sourceFile.getAbsolutePath(), target, thumbnailWidth, thumbnailHeight,
        ext.substring(1));
    return null;
  }

  public void setFileStoragePath(String fileStoragePath) {
    this.fileStoragePath = fileStoragePath;
  }

  public void setThumbnailWidth(int thumbnailWidth) {
    this.thumbnailWidth = thumbnailWidth;
  }

  public void setThumbnailHeight(int thumbnailHeight) {
    this.thumbnailHeight = thumbnailHeight;
  }

  /**
   * Start: Use Lanczos filter to replace the original algorithm for image scaling. Lanczos improves
   * quality of the scaled image modify by :blade
   */
  public BufferedImage imageZoomOut(BufferedImage srcBufferImage, int w, int h) {
    calContrib(srcBufferImage.getWidth(),w);
    BufferedImage pbOut = horizontalFiltering(srcBufferImage, w);
    return verticalFiltering(pbOut, h);
  }


  private void calContrib(int width,int scaleWidth) {
    halfDots = (int) ((double) width * SUPPORT / (double) scaleWidth);
    dots = halfDots * 2 + 1;
    try {
      contrib = new double[dots];
      normContrib = new double[dots];
      tmpContrib = new double[dots];
    } catch (Exception e) {
      LOG.error("init contrib,normContrib,tmpContrib",e);
    }
    int center = halfDots;
    contrib[center] = 1.0;
    double weight = 0.0;
    int i = 0;
    for (i = 1; i <= center; i++) {
      contrib[center + i] = lanczos(i, width, scaleWidth, SUPPORT);
      weight += contrib[center + i];
    }
    for (i = center - 1; i >= 0; i--) {
      contrib[i] = contrib[center * 2 - i];
    }
    weight = weight * 2 + 1.0;
    for (i = 0; i <= center; i++) {
      normContrib[i] = contrib[i] / weight;
    }
    for (i = center + 1; i < dots; i++) {
      normContrib[i] = normContrib[center * 2 - i];
    }
  } 

  private double lanczos(int i, int inWidth, int outWidth, double support) {
    double x;
    x = (double) i * (double) outWidth / (double) inWidth;
    return Math.sin(x * Math.PI) / (x * Math.PI) * Math.sin(x * Math.PI / support)
        / (x * Math.PI / support);
  }
  
  /**
   *  处理边缘.
   */
  private void calTempContrib(int start, int stop) {
    double weight = 0;
    int i = 0;
    for (i = start; i <= stop; i++) {
      weight += contrib[i];
    }
    for (i = start; i <= stop; i++) {
      tmpContrib[i] = contrib[i] / weight;
    }
  } 

  private int getRedValue(int rgbValue) {
    int temp = rgbValue & 0x00ff0000;
    return temp >> 16;
  }

  private int getGreenValue(int rgbValue) {
    int temp = rgbValue & 0x0000ff00;
    return temp >> 8;
  }

  private int getBlueValue(int rgbValue) {
    return rgbValue & 0x000000ff;
  }

  private int comRgb(int redValue, int greenValue, int blueValue) {
    return (redValue << 16) + (greenValue << 8) + blueValue;
  }

  // 行水平滤波
  private int horizontalFilter(BufferedImage bufImg, int startX, int stopX, int start,
      int y, double[] contrib) {
    double valueRed = 0.0;
    double valueGreen = 0.0;
    double valueBlue = 0.0;
    int valueRgb = 0;
    for (int i = startX, j = start; i <= stopX; i++, j++) {
      valueRgb = bufImg.getRGB(i, y);
      valueRed += getRedValue(valueRgb) * contrib[j];
      valueGreen += getGreenValue(valueRgb) * contrib[j];
      valueBlue += getBlueValue(valueRgb) * contrib[j];
    }
    valueRgb = comRgb(clip((int) valueRed), clip((int) valueGreen), clip((int) valueBlue));
    return valueRgb;
  } 

  /**
   * 图片水平滤波.
   */
  private BufferedImage horizontalFiltering(BufferedImage bufImage, int tarW) {
    int dwInW = bufImage.getWidth();
    int dwInH = bufImage.getHeight();
    int value = 0;
    BufferedImage pbOut = new BufferedImage(tarW, dwInH, BufferedImage.TYPE_INT_RGB);
    for (int i = 0; i < tarW; i++) {
      int startX;
      int start;
      int x = (int) (((double) i) * ((double) dwInW) / ((double) tarW) + 0.5);
      int y = 0;
      startX = x - halfDots;
      if (startX < 0) {
        startX = 0;
        start = halfDots - x;
      } else {
        start = 0;
      }
      int stop;
      int stopX = x + halfDots;
      if (stopX > (dwInW - 1)) {
        stopX = dwInW - 1;
        stop = halfDots + (dwInW - 1 - x);
      } else {
        stop = halfDots * 2;
      }
      if (start > 0 || stop < dots - 1) {
        calTempContrib(start, stop);
        for (y = 0; y < dwInH; y++) {
          value = horizontalFilter(bufImage, startX, stopX, start, y, tmpContrib);
          pbOut.setRGB(i, y, value);
        }
      } else {
        for (y = 0; y < dwInH; y++) {
          value = horizontalFilter(bufImage, startX, stopX, start, y, normContrib);
          pbOut.setRGB(i, y, value);
        }
      }
    }
    return pbOut;
  } 

  private int verticalFilter(BufferedImage pbInImage, int startY, int stopY, int start,
      int x, double[] contrib) {
    double valueRed = 0.0;
    double valueGreen = 0.0;
    double valueBlue = 0.0;
    int valueRgb = 0;
    for (int i = startY, j = start; i <= stopY; i++, j++) {
      valueRgb = pbInImage.getRGB(x, i);
      valueRed += getRedValue(valueRgb) * contrib[j];
      valueGreen += getGreenValue(valueRgb) * contrib[j];
      valueBlue += getBlueValue(valueRgb) * contrib[j];
    }
    valueRgb = comRgb(clip((int) valueRed), clip((int) valueGreen), clip((int) valueBlue));
    return valueRgb;
  }

  private BufferedImage verticalFiltering(BufferedImage pbImage, int tarH) {
    int interW = pbImage.getWidth();
    int interH = pbImage.getHeight();
    int value = 0;
    BufferedImage pbOut = new BufferedImage(interW, tarH, BufferedImage.TYPE_INT_RGB);
    for (int i = 0; i < tarH; i++) {
      int startY;
      int start;
      int y = (int) (((double) i) * ((double) interH) / ((double) tarH) + 0.5);
      startY = y - halfDots;
      if (startY < 0) {
        startY = 0;
        start = halfDots - y;
      } else {
        start = 0;
      }
      int stop;
      int stopY = y + halfDots;
      if (stopY >  (interH - 1)) {
        stopY = interH - 1;
        stop = halfDots + (interH - 1 - y);
      } else {
        stop = halfDots * 2;
      }
      if (start > 0 || stop < dots - 1) {
        calTempContrib(start, stop);
        for (int x = 0; x < interW; x++) {
          value = verticalFilter(pbImage, startY, stopY, start, x, tmpContrib);
          pbOut.setRGB(x, i, value);
        }
      } else {
        for (int x = 0; x < interW; x++) {
          value = verticalFilter(pbImage, startY, stopY, start, x, normContrib);
          pbOut.setRGB(x, i, value);
        }
      }
    }
    return pbOut;
  } 

  private int clip(int x) {
    if (x < 0) {
      return 0;
    }
    if (x > 255) {
      return 255;
    }
    return x;
  }

  /**
   * End: Use Lanczos filter to replace the original algorithm for image scaling. Lanczos improves
   * quality of the scaled image modify by :blade
   */
  public boolean scale(String source, String target, int width, int height, String ext) {
    File f = new File(source);
    try {
      BufferedImage bi = ImageIO.read(f);
      BufferedImage out = null;
      ImageScale scal = new ImageScale();
      int[] widthHeight = getImageWidthAndHeight(bi.getWidth(), bi.getHeight(), width, height);
      out = scal.imageZoomOut(bi, widthHeight[0], widthHeight[1]);
      File t = new File(target);
      ImageIO.write(out, ext, t);
      return true;
    } catch (IOException e) {
      LOG.error("failed to scale image:",e);
      return false;
    }
  }

  /**
   * 得到放大或者缩小后的比例.
   *
   * @param orgW
   *          图片原宽
   * @param orgH
   *          原高
   * @param tarW
   *          转换后的宽
   * @param tarH
   *          转换后的高
   * @return 返回宽和高的数组
   */
  private int[] getImageWidthAndHeight(int orgW, int orgH, int tarW, int tarH) {
    int width = 0;
    int height = 0;
    if (orgW > 0 && orgH > 0) {
      if (orgW / orgH >= tarW / tarH) {
        if (orgW > tarW) {
          width = tarW;
          height = (orgH * tarW) / orgW;
        } else {
          width = orgW;
          height = orgH;
        }
        LOG.debug("++Widht: {} , Height: {}",width,height);
      } else {
        if (orgH > tarH) {
          height = tarH;
          width = (orgW * tarH) / orgH;
        } else {
          width = orgW;
          height = orgH;
        }
        LOG.debug("++Widht: {} , Height: {}",width,height);
      }
    }
    return new int[]{width,height};
  }
}